package pl.edu.agh.student.janikhankus.grammar;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import pl.edu.agh.student.janikhankus.exceptions.BadProbabilitySumException;
import pl.edu.agh.student.janikhankus.exceptions.DuplicatedProductionsException;
import pl.edu.agh.student.janikhankus.tree.Epsylon;
import pl.edu.agh.student.janikhankus.tree.Grammar;
import pl.edu.agh.student.janikhankus.tree.NonTerminal;
import pl.edu.agh.student.janikhankus.tree.Production;
import pl.edu.agh.student.janikhankus.tree.Symbol;
import pl.edu.agh.student.janikhankus.tree.Terminal;

/**
 * Class which is responsible for checking whether grammar contains errors.
 * 
 * @author Jaroslaw Janik
 */
public class GrammarChecker {

	/**
	 * Checks whether probabilities assigned to productions are proper and 
	 * checks if grammar contains duplicated productions.
	 * 
	 * @param grammar Grammar to be checked.
	 * 
	 * @return True if grammar is correct; False otherwise.
	 * 
	 * @throws BadProbabilitySumException
	 * @throws DuplicatedProductionsException
	 */
	public boolean checkSemantic(Grammar grammar) throws BadProbabilitySumException, DuplicatedProductionsException {
		
		boolean check;
		
		check = checkProbability(grammar);
		if (!check) {
			throw new BadProbabilitySumException();
		}
		
		check = checkDuplicatedProductions(grammar);
		if (!check) {
			throw new DuplicatedProductionsException();
		}
		
		return true;
	}
	
	private boolean checkProbability(Grammar grammar) {
		
		Map<String, Double> probabilities = new HashMap<String, Double>();
		
		List<Production> productions = grammar.getProductionList();
		
		for (Production production : productions) {
			String name = production.getLeft().getName();
			
			if (probabilities.containsKey(name)) {
				probabilities.put(name, probabilities.get(name) + production.getProbability());
			} else {
				probabilities.put(name, production.getProbability());
			}
		}
		
		for (Double probability : probabilities.values()) {
			if (probability != 1.0) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * Replaces terminals in productions which contains on the right side terminal and nonterminal at the same moment
	 * with temporary production which has on the right side only this terminal.
	 * 
	 * @param grammar Grammar to which will be added temporary productions.
	 * 
	 * @return True if success; False otherwise.
	 */
	public boolean addTemporaryProductions(Grammar grammar) {
		List<Production> productions = grammar.getProductionList();
		List<Production> newProductions = new ArrayList<Production>();
		
		for (Production production : productions) {
			ArrayList<Symbol> right = production.getRight();
			
			int size = right.size();
			
			if (size > 1) {
				for (int i = 0; i < size; ++i) {
					if ((right.get(i) instanceof Terminal) || (right.get(i) instanceof Epsylon)) {
						NonTerminal nonTerminal = new NonTerminal("TMP" + randomString());
						Production newProduction = new Production();
						newProduction.markAsTemporary();
						newProduction.setLeft(nonTerminal);
						ArrayList<Symbol> newRight = new ArrayList<Symbol>();
						newRight.add(right.get(i));
						newProduction.setRight(newRight);
						newProduction.setProbability(1.0);
						
						right.set(i, nonTerminal);
						
						newProductions.add(newProduction);
						grammar.addTemporaryProduction(newProduction);
					}
				}
			}
		}
		
		for (Production production : newProductions) {
			productions.add(production);
		}
		
		return true;
	}
	
	private boolean checkDuplicatedProductions(Grammar grammar) {
		
		Map<String, List<List<Symbol>>> symbols = new HashMap<String, List<List<Symbol>>>();
		
		List<Production> productions = grammar.getProductionList();
		
		for (Production production : productions) {
			String name = production.getLeft().getName();
			
			if (symbols.containsKey(name)) {
				List<List<Symbol>> productionsList = symbols.get(name);
				for(List<Symbol> prod : productionsList) {
					if (compareList(prod, production.getRight())) {
						return false;
					}
				}
				
				symbols.get(name).add(production.getRight());
			} else {
				List<List<Symbol>> productionsList = new LinkedList<List<Symbol>>();
				productionsList.add(production.getRight());
				symbols.put(name, productionsList);
			}
		}
		
		return true;
	}
		
	private String randomString() {
		UUID uuid = UUID.randomUUID();
		String myRandom = uuid.toString();
			
		return myRandom.substring(0, 5);
	}
	
	private boolean compareList(List<Symbol> list1, List<Symbol> list2) {
		
		if (list1.size() == list2.size()) {
			int listSize = list1.size();
			
			for (int i = 0; i < listSize; ++i) {
				if (list1.get(i).getName().compareTo(list2.get(i).getName()) != 0) {
					return false;
				}
			}
			
			return true;
		}
		
		return false;
	}
}
